import { fabric } from 'fabric';
import type { IPreSegInfo, TRegionIndex } from '../types/DrawPalette.type';
import type { PreSegInfo } from '../types/PreSegInfo.type';
import {
  IGrayMaskTranspreviewMaskOption,
  eOutputFormat,
  tInputDataType,
  tOutputDataType,
} from '../types/imageProcess.type';
import { pictureLoader } from './pictureLoader.tool';

import { MASK_COLOR, MASK_THRESHOLD } from '../constants/mask.constant';
/**
 * @description 计算图像缩放后的尺寸
 * @param {}
 * @returns {}
 */
const scaleToFitMaxDimension = (
  imgWidth: number,
  imgHeight: number,
): {
  width: number;
  height: number;
  scale: number;
} => {
  const width = imgWidth;
  const height = imgHeight;
  const scale = 1;

  return {
    width,
    height,
    scale,
  };
};

const base64ImagePrefix = 'data:image/png;base64,';

/**
 * @description: 转换预分割信息格式
 */
const transPreSegInfoFormat: (
  preSegInfo: PreSegInfo,
) => Promise<IPreSegInfo> = async (preSegInfo) => {
  const { regionIndex, regionInfo } = preSegInfo;
  const url = `${base64ImagePrefix}${regionIndex}`;
  // console.log('--regionIndex--url---:', url);
  const pixelData = await imageTo2DArrayByGray(url);
  console.log('--transPreSegInfoFormat--0.pixelData---:', pixelData);
  const palettePreSegInfo: IPreSegInfo = {
    regionIndex: pixelData || [],
    regionInfos: regionInfo.map((info) => ({
      ...info,
      region: `${base64ImagePrefix}${info.region}`,
    })),
  };
  return palettePreSegInfo;
};

/**
 * @description 将灰度图像转换为二维数组,一个像素点用一个r值代替
 * @param { HTMLImageElement | string } target  元素
 * @returns { number[][] | null } 图像二维数组 或 null
 */
async function imageTo2DArrayByGray(
  target: string | HTMLImageElement,
): Promise<TRegionIndex | null> {
  let image: HTMLImageElement | null = null;
  if (target instanceof HTMLImageElement) {
    image = target;
  }
  if (typeof target === 'string') {
    image = await pictureLoader(target);
  }
  if (!image) return null;

  const { width, height } = scaleToFitMaxDimension(image.width, image.height);
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas?.getContext('2d', {
    willReadFrequently: true, // 提示浏览器优化像素读取性能
  });
  if (!ctx) return null;
  ctx.drawImage(image, 0, 0, width, height);
  // 提取图像的像素数据,imageData:[r,g,b,a,...], 它是一个一维数组，按顺序存储了每个像素的 RGBA（红、绿、蓝、透明度）值。
  const imageData = ctx.getImageData(0, 0, width, height, {});
  // console.log('--提取图像的像素数据--imageData--:', imageData);
  const pixelData: number[][] = [];
  // 取 imageData中的R值，并按行数存储为一个二维数组
  for (let y = 0; y < height; y++) {
    // height:2px 对应index:0,1
    const rowData: number[] = [];
    for (let x = 0; x < width; x++) {
      // 每一个图块的颜色是唯一的，且只有一种颜色(内部每个像素点的rbga都是一样的) 所以可以用r值表示当前像素点
      rowData.push(imageData.data[(y * width + x) * 4]);
    }
    pixelData.push(rowData);
  }
  // width:50,height:50
  // 第一行 y:0 [( 0 * 50 + 0 ) * 4, ( 0 * 50 + 1 ) * 4, ( 0 * 50 + 2 ) * 4, ... ( 0 * 50 + 49 ) * 4  ]
  // 第二行 y:1
  return pixelData;
}

const getLocation = (
  fEvent: fabric.IEvent,
  canvas: fabric.Canvas,
): fabric.Point | null => {
  const absolutePointer = canvas.getPointer(fEvent.e);
  if (!absolutePointer) return null; // 未获取到点
  // 未获取到位置
  if ([absolutePointer.x, absolutePointer.y].includes(NaN)) return null;

  // 鼠标开始点坐标
  const startLoc = new fabric.Point(
    Math.floor(absolutePointer.x),
    Math.floor(absolutePointer.y),
  );

  return startLoc;
};

// 校验是否超出绘制区域
const checkoutIsOutArea = (
  event: fabric.IEvent,
  size: { width: number; height: number },
  canvas: fabric.Canvas,
) => {
  const { width, height } = size;
  const point = getLocation(event, canvas);
  if (!point) return true;
  if (!drawBoundCheck(point, { width, height })) return true;
  return false;
};

interface iCheckArea {
  left?: number;
  top?: number;
  width: number;
  height: number;
}
const drawBoundCheck = (point: fabric.Point, area: iCheckArea): boolean => {
  const { x, y } = point;
  const { left = 0, top = 0, width, height } = area;
  if (left <= x && top <= y && x <= width + left && y <= height + top) {
    return true;
  }
  return false;
};

export interface IPoint {
  x: number;
  y: number;
}
export type TPixelIndex = number;

/**
 * @description 根据坐标位置获取图像像素点索引
 * @param { number[][] } pixelData  图像像素点二维数据
 * @param { number } point.x  点位X轴坐标
 * @param { number } point.y  点位Y轴坐标
 * @returns { Promise<TPixelIndex> } 图像索引 或 -1
 */
const getPixelIndexByCoord = (
  pixelData: number[][],
  point: IPoint,
): TPixelIndex => {
  try {
    if (!pixelData?.length) return -1;
    if (pixelData?.length <= point.y) return -1;
    if (point.y <= 0 || point.x <= 0) return -1;
    const row = pixelData[point.y - 1];
    if (row.length < point.x) return -1;
    return pixelData[point.y - 1][point.x - 1];
  } catch (error) {
    return -1;
  }
};

/**
 * @description 创建Canvas
 * @param { CanvasImageSource } imageSource Canvas image source
 * @param { number } width  图像宽度
 * @param { number } height  图像高度
 * @returns { HTMLCanvasElement | null } 创建出的Canvas
 */
const createCanvas = (
  data: CanvasImageSource | ImageData,
  width: number,
  height: number,
): HTMLCanvasElement => {
  let imageSource = data;
  if (imageSource instanceof ImageData) {
    const imageData = imageSource as ImageData;
    const canvas = document.createElement('canvas');
    canvas.width = imageData.width;
    canvas.height = imageData.height;
    const ctx = canvas.getContext('2d', {
      colorSpace: 'srgb',
      willReadFrequently: true,
    });
    ctx?.putImageData(imageData, 0, 0);
    imageSource = canvas;
  }

  if (
    imageSource instanceof HTMLCanvasElement &&
    width === imageSource.width &&
    height === imageSource.height
  ) {
    return imageSource;
  }

  const scaledCanvas = document.createElement('canvas');
  scaledCanvas.width = width;
  scaledCanvas.height = height;
  const ctx = scaledCanvas.getContext('2d', {
    colorSpace: 'srgb',
  });
  ctx?.drawImage(imageSource, 0, 0, width, height);
  return scaledCanvas;
};

/**
 * @description 获取图像像素点数据
 * @param { string | HTMLImageElement } mask  图像base64数组
 * @returns { Promise<{
 *  canvas:  HTMLCanvasElement;
 *  ctx: CanvasRenderingContext2D;
 *  img: HTMLImageElement;
 *  imageData: ImageData
 * } | null> } 图像base64数组 或 null
 */
export const maskToImageData = async (
  mask: tInputDataType,
): Promise<{
  canvas: HTMLCanvasElement;
  ctx: CanvasRenderingContext2D;
  imageData: ImageData;
} | null> => {
  if (mask instanceof HTMLCanvasElement) {
    return {
      canvas: mask,
      ctx: mask.getContext('2d', {
        colorSpace: 'srgb',
        willReadFrequently: true, //优化频繁读取像素数,当设置为true时，浏览器会缓存像素数据以加速后续读取操作
      }) as CanvasRenderingContext2D,
      imageData: mask
        .getContext('2d', {
          colorSpace: 'srgb',
          willReadFrequently: true,
        })
        ?.getImageData(0, 0, mask.width, mask.height, {
          colorSpace: 'srgb',
        }) as ImageData,
    };
  }

  let img: HTMLImageElement;
  if (typeof mask === 'string') {
    img = await pictureLoader(mask);
  } else {
    img = mask;
  }
  if (!img) return null;

  const { width, height } = scaleToFitMaxDimension(img.width, img.height);
  const canvas = createCanvas(img, width, height);
  const ctx = canvas?.getContext('2d', {
    colorSpace: 'srgb',
    willReadFrequently: true,
  });
  if (!canvas || !ctx) return null;

  const imageData = ctx?.getImageData(0, 0, width, height, {
    colorSpace: 'srgb',
  });
  return { canvas, ctx, imageData };
};

/**
 * @description 灰度图转换预览图
 * @param {}
 * @returns {}
 */
const transGrayMaskToPreviewMask = async (
  mask: tInputDataType,
  option?: IGrayMaskTranspreviewMaskOption,
): Promise<tOutputDataType> => {
  const {
    selectionColor = { r: 0, g: 0, b: 0, a: 0 }, // r: 77, g: 130, b: 255, a: 0.6
    backgroundColor = { r: 255, g: 255, b: 255, a: 0.7 }, // { r: 0, g: 0, b: 0, a: 0 }
    gridColor = { r: 98, g: 98, b: 98, a: 0.52 },
    pixelBlockQuantity = 80,
    style = 'mask', // 'mask' | 'grid'
    outputFormat = eOutputFormat.canvas, // 'canvas'
  } = option || {};

  const selectionColorData = [
    selectionColor.r,
    selectionColor.g,
    selectionColor.b,
    selectionColor.a * 255, // 透明度 0-1 改成 0-255
  ];

  const backgroundColorData = [
    backgroundColor.r,
    backgroundColor.g,
    backgroundColor.b,
    backgroundColor.a * 255,
  ];

  const gridColorData = [
    gridColor.r,
    gridColor.g,
    gridColor.b,
    gridColor.a * 255,
  ];

  const res = await maskToImageData(mask);
  if (!res) return null;

  const { canvas, ctx, imageData: maskImageData } = res;
  const { width, height } = canvas;
  const { data } = maskImageData; // [r,g,b,a,...]
  const newData = new Uint8ClampedArray(data.length);

  // 初始化全图网格
  if (style === 'grid') {
    const imgWidth = width;
    const imgHeight = height;
    const pixelBlockSize = Math.floor(width / pixelBlockQuantity);
    const lineData = new Uint8ClampedArray(imgWidth * 4);
    let shouldMask = false;
    let xBlock = 0;
    let yBlock = 0;

    for (let y = 0; y < imgHeight; y++) {
      if (y % pixelBlockSize === 0) {
        yBlock = Math.floor(y / pixelBlockSize);

        for (let x = 0, i = 0; x < imgWidth; x++, i += 4) {
          if (x % pixelBlockSize === 0) {
            xBlock = Math.floor(x / pixelBlockSize);
            shouldMask = (xBlock + yBlock) % 2 === 0;
          }
          if (shouldMask) {
            lineData.set(gridColorData, i);
          } else {
            lineData.set(backgroundColorData, i);
          }
        }
      }

      newData.set(lineData, y * imgWidth * 4);
    }
  }

  // 选中区域像素点设置
  const shouldSetBgColor = style !== 'grid' && backgroundColorData[3] !== 0;
  for (let i = 0; i < data.length; i += 4) {
    // 遍历像素点中的r值
    if (data[i] > MASK_THRESHOLD) {
      //255 * 0.5;   check 图像 mask 阈值
      newData.set(selectionColorData, i);
    } else if (shouldSetBgColor) {
      newData.set(backgroundColorData, i);
    }
  }

  ctx.putImageData(new ImageData(newData, width, height), 0, 0);
  return await canvasToOutput(canvas, outputFormat); // return canvas
};

/**
 * @description 将Canvas转换为输出格式
 * @param { HTMLCanvasElement } canvas  图像元素
 * @param { eOutputFormat } outputFormat  输出格式
 * @returns { Promise<HTMLCanvasElement | string | Blob | null> } 输出数据 或 null
 */
const canvasToOutput = async (
  canvas: HTMLCanvasElement,
  outputFormat: eOutputFormat,
): Promise<HTMLCanvasElement | string | Blob | null> => {
  switch (outputFormat) {
    case eOutputFormat.canvas:
      return canvas;
    case eOutputFormat.base64:
      return canvas.toDataURL('image/jpeg');
    case eOutputFormat.blob:
      return new Promise((resolve) => {
        canvas.toBlob((blob) => {
          resolve(blob);
        }, 'image/jpeg');
      });
    default:
      return null;
  }
};

/**
 * @description 合并灰度图
 * @param { number[][] } pixelData  图像像素点二维数据
 * @param { string } base64Mask  图像base64数组
 * @param { { color: { r: number, g: number, b: number, a: number } } } option  图像base64数组
 * @returns { Promise<{ pixelData: number[][]; image: HTMLImageElement } | null> } 图像base64数组 或 null
 */
const mergeSegMask = async (
  pixelData: Uint8ClampedArray | null | undefined,
  mask: tInputDataType,
  option?: {
    color?: { r: number; g: number; b: number; a: number };
    fabricCanvas?: fabric.Canvas;
    hover?: boolean;
    hoverFillColor?: { r: number; g: number; b: number; a: number };
  },
): Promise<{
  pixelData: Uint8ClampedArray;
  image: fabric.Object | null;
} | null> => {
  const res = await maskToImageData(mask);
  if (!res) return null;

  const { canvas, imageData } = res;
  const { data } = imageData;
  const { color, hoverFillColor } = option || {};
  const fillColor = color || hoverFillColor || MASK_COLOR;

  const newPixelData = pixelData
    ? new Uint8ClampedArray(pixelData)
    : new Uint8ClampedArray(data.length);
  for (let i = 0; i < data.length; i += 4) {
    if (data[i + 3] || newPixelData[i + 3]) {
      newPixelData.set(
        [
          fillColor.r,
          fillColor.g,
          fillColor.b,
          Math.max(data[i + 3], newPixelData[i + 3]),
        ],
        i,
      );
    }
  }

  const newImageData = new ImageData(newPixelData, canvas.width, canvas.height);

  let image: fabric.Object | null = null;

  const c = document.createElement('canvas');
  c.width = canvas.width;
  c.height = canvas.height;

  c.getContext('2d')!.putImageData(newImageData, 0, 0);

  image = new fabric.Image(c, {
    selectable: false,
  });

  return {
    pixelData: newPixelData,
    image,
  };
};

export {
  checkoutIsOutArea,
  getLocation,
  getPixelIndexByCoord,
  mergeSegMask,
  scaleToFitMaxDimension,
  transGrayMaskToPreviewMask,
  transPreSegInfoFormat,
};
